#include <SPI.h>
#include <Ethernet.h>
#include "ArduinoInterOp.h"
#include "SetupData.h"
#include "ArduinoUtils.h"

#define COMMAND_LENGTH 0x41

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(10,0,0,3);
EthernetServer server(80);

connectionType type = NotSet;
SetupData PinsData;


void setup()
{
	InitalizeListeners();
	
	WaitForReady();
	WaitForSetup();
}
void InitalizeListeners()
{
	Serial.begin(9600);
	Ethernet.begin(mac, ip);
	server.begin();
}

void WriteResponse(String response, readData assembledCommandData)
{
	switch(type)
	{
	case NotSet:
		break;
	case Http:
		assembledCommandData.Client.println("HTTP/1.1 200 OK");
		assembledCommandData.Client.println("Content-Type: text/html");
		assembledCommandData.Client.println("Connnection: close");
		assembledCommandData.Client.println();
		assembledCommandData.Client.println(response);
		assembledCommandData.Client.println();

		delay(10);

		assembledCommandData.Client.stop();
		break;
	case SerialPort:
		Serial.println(response);
		break;
	}
}

void WaitForSetup()
{
	String responseStatus = "";
	while (true)
	{
		readData receivedCommand = AssembleCommandString();
		String commandString = receivedCommand.Command;

		if(commandString == "setupfinish")
		{
			WriteResponse("setupfinish",receivedCommand);
			break;
		}
		else if(commandString != "")
		{
			PinsData.ParseFromString(commandString);

			int numberOfPins = PinsData.GetNumberOfConfiguredPins();
			if(numberOfPins > 0 )
			{
				String comma = "";

				for(int i = 0 ; i < numberOfPins ; ++i)
				{
					pinData * data = PinsData.GetPinDataFromIndex(i);

					switch(data->type)
					{
					case Input:
						pinMode(data->pin, INPUT);
						data->SetValue(GetSensorValue(data->pin));
						break;
					case Output:
						pinMode(data->pin, OUTPUT);	
						break;
					}
				}

				commandData returnedSetupData;
				returnedSetupData.Data = PinsData.GetAllPinValuesAsCommadValues();
				
				WriteResponse(GetCommandResponseString(returnedSetupData, false), receivedCommand);
			}
		}
	}
}

void WaitForReady()
{
	bool ready = false;

	while (!ready)
	{
		readData commandData = AssembleCommandString();

		if(commandData.Command == "ack")
		{
			WriteResponse("syn", commandData);
			break;
		}
	}
}

readData AssembleCommandString()
{
	readData returnedData;
	returnedData.Command = "";

	int index = 0;
	char inputChar;

	if(type == NotSet || type == SerialPort)
	{
		// Check the serial port
		if(Serial.available() > 0)
		{
			delay(20);
			returnedData.Command = Serial.readStringUntil('\n');

			if(type == NotSet)
			{
				type = SerialPort;
			}
		}
	}

	if(returnedData.Command == "")
	{
		if(type == NotSet || type == Http)
		{
			EthernetClient client = server.available();

			if(client)
			{
				if (client.connected()) 
				{
					if(type == NotSet)
					{
						type = Http;
					}

					String line("");
					int index = 0;
					int index2 = 0;					

					while (client.available()) 
					{
						delay(20);

						line = client.readStringUntil('\n');	
						index = line.indexOf("!");
						
						returnedData.Client = client;

						if(index != -1)
						{
							index++;
							index2 = line.indexOf("@");

							client.flush();
							break;	
						}
					}

					returnedData.Command = line.substring(index, index2);
				}
			}
		}
	}

	return returnedData;

}

commandValues GetSingleCommand(String commandString)
{
	commandValues data;
	unsigned long commandNumber = commandString.toInt();

	data.command = commandNumber >> 24; // Get Command ID
	data.pin = (commandNumber << 8) >> 24; // Get rid of command and get sensor
	data.value = (commandNumber << 16) >> 22; // Get rid of command and sensor and take the value (Notice: not taking the 6 'free' bits)

	return data;
}

vector<commandValues> GetCommands(String commands)
{
	vector<commandValues> values;
	int lastIndex = 0;
	int commandIndex = commands.indexOf("-");

	while(commandIndex != -1)
	{
		String singleCommand = commands.substring(lastIndex, commandIndex);

		values.push_back(GetSingleCommand(singleCommand));

		lastIndex = commandIndex + 1;
		commandIndex = commands.indexOf("-", lastIndex);
	}

	return values;
}

commandData GetCommand(String commandString)
{
	int ticksIndex = commandString.indexOf(";");
	commandData data;
	if(ticksIndex != -1)
	{
		String commands = commandString.substring(0, ticksIndex);
		String commandOperationTicksSegment = commandString.substring(ticksIndex + 1, commandString.length());

		data.Data = GetCommands(commands);

		data.operationTicks = commandOperationTicksSegment;
	}

	return data;
}

String GetResponseString(commandValues data)
{
	unsigned long responseData = data.command;
	responseData = responseData << 8;
	responseData = responseData | data.pin;
	responseData = responseData << 10;
	responseData = responseData | data.value;
	responseData = responseData << 6; // To match command pattern

	String valueString (responseData);

	return valueString;
}

String ConvertResponseToSting(commandValues command)
{
	return GetResponseString(command) + "-";
}

String GetCommandResponseString(commandData responseData, bool includeOperationTicksAndId)
{
	String responseString;

	int numberOfCommands = responseData.Data.size();

	for(int j = 0 ; j < numberOfCommands ; ++j)
	{
		responseString += ConvertResponseToSting(responseData.Data[j]);
	}

	if(includeOperationTicksAndId)
	{
		responseString += ";" + responseData.operationTicks;
	}

	return responseString;
}

commandValues PerformCommand(commandValues operationValues)
{
	commandValues returnValue;
	pinData * aaa;
	returnValue.pin =operationValues.pin;
	returnValue.command = operationValues.command;

	switch (operationValues.command)
	{
	case GET_SENSOR_VALUE:
		returnValue.value = GetSensorValue(operationValues.pin);
		PinsData.GetPinDataFromPinNumber(operationValues.pin)->SetValue(returnValue.value);

		break;
	case SET_OUTPUT_VALUE:
		SetOutput(operationValues.pin, operationValues.value);
		returnValue.value = operationValues.value;
		PinsData.GetPinDataFromPinNumber(operationValues.pin)->SetValue(returnValue.value);
		break;
	case LISTEN_TO_SENSOR_CHANGE:
		break;
	default:
		break;
	}

	return returnValue;
}

commandData GetAllSensorsReturnData()
{
	commandData data;

	data.Data = PinsData.GetAllPinValuesAsCommadValues();

	return data;
}


void loop()
{ 
	readData assembledCommandData = AssembleCommandString();

	if(assembledCommandData.Command != "")
	{
		String responseString("");

		// For some reason the handler got a reset and asking to reconnect. Just return the state according to the known setup sensor data
		if(assembledCommandData.Command == "ack")
		{
			commandData returnData = GetAllSensorsReturnData();
			responseString = GetCommandResponseString(returnData, false);			
		}
		else
		{
			commandData cmd = GetCommand(assembledCommandData.Command);
			commandData returnData;
			returnData.operationTicks = cmd.operationTicks;

			int numberOfCommands = cmd.Data.size();

			for(int j = 0 ; j < numberOfCommands ; ++j)
			{
				commandValues value = PerformCommand(cmd.Data[j]);

				returnData.Data.push_back(value);
			}

			responseString = GetCommandResponseString(returnData, true);
		}

		WriteResponse(responseString, assembledCommandData);
	}
}

